package com.edgar.common.lib.app;

import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;

import com.edgar.loglib.LogUtils;

/**
 * Crash异常收集类
 * @author Roc
 *
 */
public class CrashHandler implements UncaughtExceptionHandler {

	private static final String TAG = CrashHandler.class.getSimpleName();
	/** 是否上传到友盟 */
	private final boolean UMENG_FLAG = false;
	/** 用来存储设备信息和异常信息 */
	private Map<String, String> infos = new HashMap<String, String>();
	/** 用于格式化日期,作为日志文件名的一部分 */
	private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.getDefault());
	private static final String CRASH_FILE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath()+
			File.separator+LogUtils.getLogConfig().getCrashPath();

	private UncaughtExceptionHandler mDefaultHandler;

	private Context context;
	/** 单实例 */
	private static CrashHandler instance;  

	private CrashHandler() {}

	/**   
	 * 初始化,注册Context对象,   
	 * 获取系统默认的UncaughtException处理器,   
	 * 设置该CrashHandler为程序的默认处理器   
	 * @param context
	 */    
	public void init(Context context) {    
		this.context = context;
		mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		// 如果开了日志，则报错异常也会起开启手机进本地
		Thread.setDefaultUncaughtExceptionHandler(this);
	}

	public static CrashHandler getInstance() {
		if (instance ==  null) {
			instance = new CrashHandler();
		}
		return instance;
	}


	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		if (!handleException(ex) && mDefaultHandler != null) {  
			//如果用户没有处理则让系统默认的异常处理器来处理  
			mDefaultHandler.uncaughtException(thread, ex);  
		} else {  
			//退出程序
			android.os.Process.killProcess(android.os.Process.myPid());  
			System.exit(1);  
		} 
	}

	/** 
	 * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成. 
	 *  
	 * @param ex 
	 * @return true:如果处理了该异常信息;否则返回false. 
	 */  
	private boolean handleException(Throwable ex) {  
		if (ex == null) {  
			return false;  
		}  
		//收集设备参数信息   
		collectDeviceInfo(context);  
		//保存日志文件   
		saveCrashInfo2File(ex);  
		return true;  
	}  

	/** 
	 * 收集设备参数信息 
	 * @param ctx 
	 */  
	public void collectDeviceInfo(Context ctx) {  
		try {  
			PackageManager pm = ctx.getPackageManager();  
			PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);  
			if (pi != null) {  
				String versionName = pi.versionName == null ? "null" : pi.versionName;  
				String versionCode = pi.versionCode + "";  
				infos.put("versionName", versionName);  
				infos.put("versionCode", versionCode);  
			}  
		} catch (NameNotFoundException e) {  
			LogUtils.e(TAG,"an error occured when collect package info/n" + e.toString());
		}  
		Field[] fields = Build.class.getDeclaredFields();  
		for (Field field : fields) {  
			try {  
				field.setAccessible(true);  
				infos.put(field.getName(), field.get(null).toString());  
			} catch (Exception e) {
				LogUtils.e(TAG,"an error occured when collect crash info/n" + e.toString());
			}  
		}  
	}  

	/** 
	 * 保存错误信息到文件中 
	 *  
	 * @param ex 
	 * @return  返回文件名称,便于将文件传送到服务器 
	 */  
	private String saveCrashInfo2File(Throwable ex) {  
		StringBuffer sb = new StringBuffer();  
		for (Map.Entry<String, String> entry : infos.entrySet()) {  
			String key = entry.getKey();  
			String value = entry.getValue();  
			sb.append(key + "=" + value + "\n");  
		}  

		Writer writer = new StringWriter();  
		PrintWriter printWriter = new PrintWriter(writer);  
		ex.printStackTrace(printWriter);  
		Throwable cause = ex.getCause();  
		while (cause != null) {  
			cause.printStackTrace(printWriter);  
			cause = cause.getCause();  
		}  
		printWriter.close();  
		String result = writer.toString();  
		sb.append(result);
		LogUtils.e(TAG, "...\n" + result);
		try {  
			long timestamp = System.currentTimeMillis();  
			String time = formatter.format(new Date());  
			String fileName = "crash-" + time + "-" + timestamp + ".log";  
			if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {  
				String path = CRASH_FILE_PATH;
				File dir = new File(path);
				if (!dir.exists()) {
					if(dir.mkdirs()){}
				}
				FileOutputStream fos = new FileOutputStream(path + fileName);  
				fos.write(sb.toString().getBytes());  
				fos.close();  
			}
			return fileName;  
		} catch (Exception e) {
			LogUtils.e(TAG,"an error occured while writing file.../n" + e.toString());
		}  
		return null;  
	}  
}
